<?php
/**
 * Parse DocBlock
 *
 * Parse DocBlock from string to structure.
 *
 * $Id: Docblock.php 116 2011-09-07 13:51:39Z leikou@163.com $
 */



/**
 * Parse DocBlock from string to array.
 * 
 * @copyright Copyright (c) leikou@163.com
 */
 
class Docblock
{
    const DOCBLOCK_TEXT = 1;
    const DOCBLOCK_TAG = 2;


    public static function parse($doc_comment)
    {
        $tokens = $comments = $tags = array();
        $tag_names = $tag_datas = array();

        $doc_lines = self::doc2line($doc_comment);
        foreach($doc_lines as $line)
        {
            $line_tokens = self::line2token($line);
            $tokens = array_merge($tokens, $line_tokens);
        }

        foreach($tokens as $token)
        {
            list($type, $data) = $token;
            switch($type)
            {
                case self::DOCBLOCK_TEXT:
                    // If not tag, data must be comment
                    if( ! isset($tagid))
                    {
                        $comments[] = $data;
                    }
                    else
                    {
                        // If tag aready exists, add data to it
                        if(array_key_exists($tagid, $tag_datas))
                        {
                            $tag_datas[$tagid] .= ' ' . trim($data);
                        }
                        else
                        {
                            $tag_datas[$tagid] = trim($data);
                        }
                    }

                    break;

                case self::DOCBLOCK_TAG:
                    // Set unique id for each tag
                    $tagid = uniqid();

                    $tag_names[$tagid] = trim($data);

                    break;
            }
        }

        // Join tag_name and tag_data
        foreach($tag_names as $tagid => $name)
        {
            $data = isset($tag_datas[$tagid]) ? $tag_datas[$tagid] : NULL;
            // Create array for multi tags which has the same name
            if(array_key_exists($name, $tags))
            {
                if( ! is_array($tags[$name]))
                {
                    $old_data = $tags[$name];
                    $tags[$name] = array();
                    $tags[$name][] = $old_data;
                }

                $tags[$name][] = $data;

            }
            else
            {
                $tags[$name] = $data;
            }
        }

        return array($comments, $tags);
    }


    /**
     * Transfer docblock from string to lines, ignore empty line
     *
     * @param string $doc_comment docblock
     *
     * @return array lines array
     */
    public static function doc2line($doc_comment)
    {
        $pattern = '/^\s*\*(( |\t)*\S+.*)$/m';

        $match = array();
        $match_num = preg_match_all($pattern, $doc_comment, $match);
        $doc_lines =& $match[1];
        array_pop($doc_lines);

        return $doc_lines;
    }


    /**
     * Parse docline from string to token array.
     * Each token array element is a array which has two elements:
     *      the first element is docblock type (refer const DOCBLOCK_XXX),
     *      the second element is docblock data
     *
     * @param string $doc_comment docblock str
     *
     * @return array
     */
    public static function line2token($item)
    {
        $parse_array = array();

        $naked_item = trim($item);
        $item_array = preg_split('/:?\s/', $naked_item, 2);

        // Check whether element is tag
        if(preg_match('/^@\w+$/', $item_array[0]))
        {
            $parse_array[] = array(self::DOCBLOCK_TAG, trim($item_array[0], '@'));
            if(count($item_array) == 2)
            {
                $parse_array[] = array(self::DOCBLOCK_TEXT, $item_array[1]);
            }

        }
        else
        {
            $parse_array[] = array(self::DOCBLOCK_TEXT, $naked_item);
        }

        return $parse_array;
    }
}


/* End of file: parse_docblock */
